using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Threading;

namespace Supremacy.Client.Controls
{
    public class GamePopup : Control
    {
        internal static RoutedEventHandler CloseOnUnloadedHandler;

        public static DependencyProperty ChildProperty;
        public static DependencyProperty IsOpenProperty;
        public static DependencyProperty PlacementProperty;
        public static DependencyProperty CustomPopupPlacementCallbackProperty;
        public static DependencyProperty StaysOpenProperty;
        public static DependencyProperty HorizontalOffsetProperty;
        public static DependencyProperty VerticalOffsetProperty;
        public static DependencyProperty PlacementTargetProperty;
        public static DependencyProperty PlacementRectangleProperty;

        private GamePopupRoot _popupRoot;
        private bool _isCaptureHeld;
        private bool _isOpen;

        #region Constructors and Finalizers
        static GamePopup()
        {
            ChildProperty = DependencyProperty.Register(
                "Child",
                typeof(UIElement),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    null,
                    OnChildChanged));

            IsOpenProperty = DependencyProperty.Register(
                "IsOpen",
                typeof(bool),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    false,
                    FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                    OnIsOpenChanged,
                    CoerceIsOpen));

            PlacementProperty = DependencyProperty.Register(
                "Placement",
                typeof(PlacementMode),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    PlacementMode.Bottom,
                    OnPlacementChanged),
                IsValidPlacementMode);

            CustomPopupPlacementCallbackProperty = DependencyProperty.Register(
                "CustomPopupPlacementCallback",
                typeof(CustomPopupPlacementCallback),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(null));

            StaysOpenProperty = DependencyProperty.Register(
                "StaysOpen",
                typeof(bool),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    true,
                    OnStaysOpenChanged));

            HorizontalOffsetProperty = DependencyProperty.Register(
                "HorizontalOffset",
                typeof(double),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    0.0,
                    OnOffsetChanged));

            VerticalOffsetProperty = DependencyProperty.Register(
                "VerticalOffset",
                typeof(double),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    0.0,
                    OnOffsetChanged));

            PlacementTargetProperty = DependencyProperty.Register(
                "PlacementTarget",
                typeof(UIElement),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    null,
                    OnPlacementTargetChanged));

            PlacementRectangleProperty = DependencyProperty.Register(
                "PlacementRectangle",
                typeof(Rect),
                typeof(GamePopup),
                new FrameworkPropertyMetadata(
                    Rect.Empty,
                    OnOffsetChanged));
        }
        #endregion

        #region Events

        #region Opened Event
        public static RoutedEvent OpenedEvent = EventManager.RegisterRoutedEvent(
            "Opened",
            RoutingStrategy.Direct,
            typeof(RoutedEventHandler),
            typeof(GamePopup));

        public event RoutedEventHandler Opened
        {
            add { AddHandler(OpenedEvent, value); }
            remove { RemoveHandler(OpenedEvent, value); }
        }

        protected void OnOpened()
        {
            RaiseEvent(new RoutedEventArgs(OpenedEvent, this));
        }
        #endregion

        #region Closed Event
        public static RoutedEvent ClosedEvent = EventManager.RegisterRoutedEvent(
            "Closed",
            RoutingStrategy.Direct,
            typeof(RoutedEventHandler),
            typeof(GamePopup));

        public event RoutedEventHandler Closed
        {
            add { AddHandler(ClosedEvent, value); }
            remove { RemoveHandler(ClosedEvent, value); }
        }

        protected void OnClosed()
        {
            RaiseEvent(new RoutedEventArgs(ClosedEvent, this));
        }
        #endregion

        #endregion

        #region Properties

        internal GamePopupSite RegisteredPopupSite { get; set; }
        internal GamePopupCloseReason LastCloseReason { get; set; }

        public UIElement Child
        {
            get { return (UIElement)GetValue(ChildProperty); }
            set { SetValue(ChildProperty, value); }
        }

        public bool IsOpen
        {
            get { return (bool)GetValue(IsOpenProperty); }
            set { SetValue(IsOpenProperty, value); }
        }

        public bool StaysOpen
        {
            get { return (bool)GetValue(StaysOpenProperty); }
            set { SetValue(StaysOpenProperty, value); }
        }

        public double HorizontalOffset
        {
            get { return (double)GetValue(HorizontalOffsetProperty); }
            set { SetValue(HorizontalOffsetProperty, value); }
        }

        public double VerticalOffset
        {
            get { return (double)GetValue(VerticalOffsetProperty); }
            set { SetValue(VerticalOffsetProperty, value); }
        }

        public PlacementMode Placement
        {
            get { return (PlacementMode)GetValue(PlacementProperty); }
            set { SetValue(PlacementProperty, value); }
        }

        public UIElement PlacementTarget
        {
            get { return (UIElement)GetValue(PlacementTargetProperty); }
            set { SetValue(PlacementTargetProperty, value); }
        }

        public Rect PlacementRectangle
        {
            get { return (Rect)GetValue(PlacementRectangleProperty); }
            set { SetValue(PlacementRectangleProperty, value); }
        }

        #endregion

        private static void OnPlacementTargetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var popup = (GamePopup)d;
            if (popup.IsOpen)
                popup.UpdatePlacementTargetRegistration((UIElement)e.OldValue, (UIElement)e.NewValue);
            else if (e.OldValue != null)
                UnregisterPopupFromPlacementTarget(popup, (UIElement)e.OldValue);
        }

        protected override void OnVisualParentChanged(DependencyObject oldParent)
        {
            base.OnVisualParentChanged(oldParent);

            var registeredPopupSite = this.RegisteredPopupSite;
            if (registeredPopupSite != null)
                registeredPopupSite.Popups.Remove(this);
        }

        private void UpdatePlacementTargetRegistration(UIElement oldValue, UIElement newValue)
        {
            if (oldValue != null)
                UnregisterPopupFromPlacementTarget(this, oldValue);
            if ((newValue != null) && (VisualTreeHelper.GetParent(this) == null))
                RegisterPopupWithPlacementTarget(this, newValue);
        }

        private static void UnregisterPopupFromPlacementTarget(GamePopup popup, UIElement placementTarget)
        {
            var popupSite = placementTarget.FindVisualAncestorByType<GamePopupSite>();
            if (popupSite == null)
                return;

            popupSite.SizeChanged -= popup.OnPopupSiteSizeChanged;
            popupSite.Popups.Remove(popup);
        }

        private static void RegisterPopupWithPlacementTarget(GamePopup popup, UIElement placementTarget)
        {
            var popupSite = placementTarget.FindVisualAncestorByType<GamePopupSite>();
            if (popupSite == null)
                return;

            popupSite.Popups.Add(popup);
            popupSite.SizeChanged += popup.OnPopupSiteSizeChanged;
        }

        private void OnPopupSiteSizeChanged(object sender, SizeChangedEventArgs e)
        {
            this.Reposition();
        }

        private static void OnStaysOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var popup = (GamePopup)d;

            if (!popup.IsOpen)
                return;

            if ((bool)e.NewValue)
                popup.ReleasePopupCapture();
            else
                popup.EstablishPopupCapture();
        }

        private void ReleasePopupCapture()
        {
            if (!_isCaptureHeld)
                return;

            if (Mouse.Captured == _popupRoot)
                Mouse.Capture(null);

            _isCaptureHeld = false;
        }

        private void EstablishPopupCapture()
        {
            if (_isCaptureHeld || (_popupRoot == null) || this.StaysOpen || Mouse.Captured != null)
                return;

            Mouse.Capture(_popupRoot, CaptureMode.SubTree);
            _isCaptureHeld = true;
        }

        private static void OnOffsetChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((GamePopup)d).Reposition();
        }

        private static void OnPlacementChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((GamePopup)d).Reposition();
        }

        private static bool IsValidPlacementMode(object o)
        {
            var mode = (PlacementMode)o;
            if (((((mode != PlacementMode.Absolute) && (mode != PlacementMode.AbsolutePoint)) &&
                  ((mode != PlacementMode.Bottom) && (mode != PlacementMode.Center))) &&
                 (((mode != PlacementMode.Mouse) && (mode != PlacementMode.MousePoint)) &&
                  ((mode != PlacementMode.Relative) && (mode != PlacementMode.RelativePoint)))) &&
                (((mode != PlacementMode.Right) && (mode != PlacementMode.Left)) && (mode != PlacementMode.Top)))
            {
                return (mode == PlacementMode.Custom);
            }
            return true;
        }

        private static object CoerceIsOpen(DependencyObject d, object value)
        {
            if ((bool)value)
            {
                var popup = (GamePopup)d;
                if (!popup.IsLoaded && (VisualTreeHelper.GetParent(popup) != null))
                {
                    popup.RegisterToOpenOnLoad();
                    return false;
                }
            }
            return value;
        }

        private void RegisterToOpenOnLoad()
        {
            base.Loaded += this.OpenOnLoad;
        }

        private void OpenOnLoad(object sender, RoutedEventArgs e)
        {
            base.Dispatcher.BeginInvoke(
                DispatcherPriority.Input,
                (Action<DependencyProperty>)this.CoerceValue,
                IsOpenProperty);
        }

        private static void OnIsOpenChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var popup = (GamePopup)d;
            var newValue = (bool)e.NewValue;
            var popupSite = popup.RegisteredPopupSite;

            if (newValue)
            {
                if (popup._isOpen)
                    throw new InvalidOperationException("GamePopup is already open.");
                if (popupSite == null)
                    throw new InvalidOperationException("GamePopup has not been registered with a GamePopupSite.");

                if (CloseOnUnloadedHandler == null)
                    CloseOnUnloadedHandler = new RoutedEventHandler(CloseOnUnloaded);

                popup.Unloaded += CloseOnUnloadedHandler;

                popupSite.AddCanvasChild(popup);

                popup.OnOpened();
            }
            else
            {
                if (popupSite != null)
                    popupSite.RemoveCanvasChild(popup);

                if (CloseOnUnloadedHandler != null)
                    popup.Unloaded -= CloseOnUnloadedHandler;

                popup.OnClosed();
            }
        }

        private static void CloseOnUnloaded(object sender, RoutedEventArgs e)
        {
            ((GamePopup)sender).IsOpen = false;
        }

        private static void OnChildChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var popup = (GamePopup)d;
            var oldValue = (UIElement)e.OldValue;
            var newValue = (UIElement)e.NewValue;

            if ((popup._popupRoot != null) && popup.IsOpen)
                popup._popupRoot.Child = newValue;

            popup.RemoveLogicalChild(oldValue);
            popup.AddLogicalChild(newValue);
            popup.Reposition();
        }

        internal void Reposition()
        {
            if (!this.IsOpen)
                return;

            if (base.CheckAccess())
            {
                this.UpdatePosition();
            }
            else
            {
                this.Dispatcher.BeginInvoke(
                    DispatcherPriority.Normal,
                    (Action)UpdatePosition);
            }
        }

        private void UpdatePosition()
        {
        }

        private Visual GetTarget()
        {
            return this.PlacementTarget ?? this.FindVisualAncestorByType<Visual>();
        }

        private static bool IsAbsolutePlacementMode(PlacementMode placement)
        {
            switch (placement)
            {
                case PlacementMode.AbsolutePoint:
                case PlacementMode.Mouse:
                case PlacementMode.MousePoint:
                case PlacementMode.Absolute:
                    return true;
            }
            return false;
        }
    }
}